package blue.mqtt4j.core.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author Jin Zheng
 * @since 1.0 2020-10-10
 */
public class NioServer
{
	private final int port;
	private ServerSocketChannel serverSocketChannel;
	private Selector selector;

	public NioServer(int port)
	{
		this.port = port;
	}

	private void init()
	{
		InetSocketAddress address = new InetSocketAddress(port);
		try
		{
			serverSocketChannel = ServerSocketChannel.open();
			serverSocketChannel.configureBlocking(false);
			serverSocketChannel.bind(address);
			selector = Selector.open();
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.printf("Start echo server, port: %d\n", port);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	public void start()
	{
		this.init();
		while (true)
		{
			try
			{
				while (selector.select(0) > 0)
				{
					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> iterator = keys.iterator();
					while (iterator.hasNext())
					{
						SelectionKey key = iterator.next();
						if (key.isAcceptable())
						{
							this.handleAccept(key);
						}
						else if (key.isReadable())
						{
							this.handleRead(key);
						}
						iterator.remove();
					}
				}
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}

	private void handleAccept(SelectionKey key) throws IOException
	{
		SocketChannel socketChannel = serverSocketChannel.accept();
		socketChannel.configureBlocking(false);
		SocketAddress remoteAddress = socketChannel.getRemoteAddress();
		System.out.printf("New client address: %s\n", remoteAddress);
		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(8096);
		socketChannel.register(selector, SelectionKey.OP_READ, byteBuffer);
	}

	private void handleRead(SelectionKey key) throws IOException
	{
		SocketChannel socketChannel = (SocketChannel) key.channel();
		ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
		byteBuffer.clear();
		int read = -1;
		while ((read = socketChannel.read(byteBuffer)) > 0)
		{
			System.out.printf("selection key: read: %d\n", read);
			byteBuffer.flip();
			byte[] buf = new byte[byteBuffer.limit()];
			byteBuffer.get(buf, 0, byteBuffer.limit());
			String text = new String(buf);
			System.out.printf("Receive: %s\n", text.trim());
			byteBuffer.flip();
			while (byteBuffer.hasRemaining())
			{
				socketChannel.write(byteBuffer);
			}
			byteBuffer.clear();
		}
		if (read < 0)
		{
			SocketAddress remoteAddress = socketChannel.getRemoteAddress();
			System.out.printf("Client close: %s\n", remoteAddress);
			socketChannel.close();
		}
	}

	public static void main(String[] args)
	{
		NioServer server = new NioServer(9000);
		server.start();
	}

}
